#region Copyright & License
// Copyright 2008-2009 BUGCOLLECT.COM
// Author: Remus Rusanu
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//                 http://bugcollect.com/terms
//
#endregion
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using System.Reflection;
using System.Security.Cryptography;

namespace bugcollect.open
{
    /// <summary>
    /// This class stores the information about a report submitted to <a href="http://bugcollect.com">bugcollect.com</a>
    /// </summary>
    public sealed class Report
    {
        #region Fields

        private Guid _uuid = Guid.NewGuid();

        /// <summary>
        /// Report global unique identifier
        /// Used to identify retried submissions
        /// </summary>
        public Guid UUID
        {
            get { return _uuid; }
            set { _uuid = value; }
        }

        private string _message;
        /// <summary>
        /// The report message.
        /// </summary>
        /// <remarks>This property is part of the report bucket hash.</remarks>
        public string Message
        {
            get { return _message; }
            set { _message = value; }
        }

        private int _severity;
        /// <summary>
        /// The report severity. <a href="http://bugcollect.com">bugcollect.com</a> does not interpret the values in any way.
        /// </summary>
        /// <remarks>This property is part of the report bucket hash.</remarks>
        public int Severity
        {
            get { return _severity; }
            set { _severity = value; }
        }

        private string _notes;
        /// <summary>
        /// Report notes. Any additional information that can be relevant to the 
        /// submitted report and varies with each individual report. 
        /// </summary>
        /// <remarks>This property is not part of the report bucket hash.</remarks>
        public string Notes
        {
            get { return _notes; }
            set { _notes = value; }
        }

        private string _application;
        /// <summary>
        /// The application name. This is automatically filled with the 
        /// current appdomain entry assembly name.
        /// </summary>
        /// <remarks>This property is part of the report bucket hash.</remarks>
        public string Application
        {
            get { return _application; }
            set { _application = value; }
        }

        private string _appVersion;
        /// <summary>
        /// The application version. This is automatically filled
        /// current appdomain entry assembly version.
        /// </summary>
        /// <remarks>This property is part of the report bucket hash.</remarks>
        public string ApplicationVersion
        {
            get { return _appVersion; }
            set { _appVersion = value; }
        }

        private string _module;
        /// <summary>
        /// The name of the module that is reporting the incident.
        /// This is automatically filled with the calling assembly name.
        /// </summary>
        /// <remarks>This property is part of the report bucket hash.</remarks>
        public string Module
        {
            get { return _module; }
            set { _module = value; }
        }

        private string _moduleVersion;
        /// <summary>
        /// The version of the module that is reporting the incident.
        /// This is automatically filled with the calling assembly version.
        /// </summary>
        /// <remarks>This property is part of the report bucket hash.</remarks>
        public string ModuleVersion
        {
            get { return _moduleVersion; }
            set { _moduleVersion = value; }
        }

        private DateTime _incidentTime;
        /// <summary>
        /// The time of the reported incident.
        /// This is automatically filled with the current time when SubmitReport method is called.
        /// </summary>
        /// <remarks>This property is not part of the report bucket hash.</remarks>
        public DateTime IncidentTime
        {
            get { return _incidentTime; }
            set { _incidentTime = value; }
        }

        private string _stackTrace;
        /// <summary>
        /// The call stack of the reported incident.
        /// This is automatically filled with the exception call stack.
        /// </summary>
        /// <remarks>This property is part of the report bucket hash.</remarks>
        public string StackTrace
        {
            get { return _stackTrace; }
            set { _stackTrace = value; }
        }

        #endregion

        #region Bucket HASH

        private volatile byte[] _hash;

        internal byte[] ComputeHash()
        {
            if (null == _hash)
            {
                using (MemoryStream ms = new MemoryStream())
                {
                    using (BinaryWriter bw = new BinaryWriter(ms, Encoding.ASCII))
                    {
                        if (null != _message) { bw.Write(_message); }
                        bw.Write(_severity);
                        if (null != _application) { bw.Write(_application); }
                        if (null != _appVersion) { bw.Write(_appVersion); }
                        if (null != _module) { bw.Write(_module); }
                        if (null != _moduleVersion) { bw.Write(_moduleVersion); }
                        if (null != _stackTrace) { bw.Write(_stackTrace); }
                        bw.Flush();
                        ms.Seek(0, SeekOrigin.Begin);

                        using (MD5 hash = MD5.Create())
                        {
                            _hash = hash.ComputeHash(ms);
                        }
                    }
                }
            }
            return _hash;
        }

        #endregion


        #region XML body writer
        private const string BugCollectNamespace = "http://bugcollect.com/schemas/report/v1.0";

        internal void WriteRequestBody(Stream requestStream)
        {
            XmlWriterSettings xmlSettings = new XmlWriterSettings();
            xmlSettings.CloseOutput = false;
            xmlSettings.Encoding = Encoding.UTF8;

            using (XmlWriter writer = XmlWriter.Create(requestStream, xmlSettings))
            {
                writer.WriteStartDocument(true);
                writer.WriteStartElement("report", BugCollectNamespace);
                WriteReport(writer);
                writer.WriteEndElement();
                writer.WriteEndDocument();
                writer.Flush();
            }
        }

        private void WriteElement(XmlWriter writer, string name, object value)
        {
            if (null != value)
            {
                writer.WriteStartElement(name);
                writer.WriteValue(value.ToString());
                writer.WriteEndElement();
            }
        }

        private void WriteReport(XmlWriter writer)
        {
            WriteElement(writer, "report_uuid", _uuid);
            WriteElement(writer, "message", _message);
            WriteElement(writer, "severity", _severity);
            WriteElement(writer, "stack", _stackTrace);
            WriteElement(writer, "notes", _notes);
            WriteElement(writer, "application", _application);
            WriteElement(writer, "application_version", _appVersion);
            WriteElement(writer, "module", _module);
            WriteElement(writer, "module_version", _moduleVersion);
            WriteElement(writer, "host_name", Environment.MachineName);
            WriteElement(writer, "os", Environment.OSVersion.Platform);
            WriteElement(writer, "os_version", Environment.OSVersion.Version);
            WriteElement(writer, "incident_time", XmlConvert.ToString(_incidentTime, "o"));
        }
        #endregion

        private void GetEntryAssemblyInfo()
        {
            Assembly entry = Assembly.GetEntryAssembly();
            if (null != entry)
            {
                AssemblyName name = entry.GetName();
                _application = name.Name;
                _appVersion = String.Format("{0}.{1}.{2}",
                        name.Version.Major, name.Version.Minor, name.Version.Build);
            }
        }

        /// <summary>
        /// Creates a new report.
        /// </summary>
        /// <remarks>
        /// This constructor initializes the 
        /// <a href="P_bugcollect_Report_IncidentTime.htm">IncidentTime</a>, 
        /// <a href="P_bugcollect_Report_Application.htm">Application</a> and 
        /// <a href="P_bugcollect_Report_AppVersion.htm">AppVersion</a> properties
        /// with the current time and the appdomain entry assembly name and version.
        /// </remarks>
        public Report()
        {
            _incidentTime = DateTime.UtcNow;
            GetEntryAssemblyInfo();
        }

        /// <summary>
        /// Creates a new report with the given notes, exception information and caling assembly.
        /// </summary>
        /// <remarks>
        /// This constructor initializes the 
        /// <a href="P_bugcollect_Report_IncidentTime.htm">IncidentTime</a>, 
        /// <a href="P_bugcollect_Report_Application.htm">Application</a> and 
        /// <a href="P_bugcollect_Report_AppVersion.htm">AppVersion</a> properties
        /// with the current time and the appdomain entry assembly name and version.
        /// The <a href="P_bugcollect_Report_Module.htm">Module</a>
        /// and <a href="P_bugcollect_Report_ModuleVersion.htm">ModuleVersion</a> 
        /// of the report are initialized with the name and version of the callingAssembly parameter.
        /// </remarks>
        /// <param name="notes">Notes associated with the report</param>
        /// <param name="e">Exception object related to the report</param>
        /// <param name="callingAssembly">Assembly that is reporting the incident</param>
        public Report(
            string notes,
            Exception e,
            Assembly callingAssembly)
        {
            _notes = notes;
            if (null != e)
            {
                _message = e.Message;
                _stackTrace = e.StackTrace;
            }
            _incidentTime = DateTime.UtcNow;
            if (null != callingAssembly)
            {
                AssemblyName name = callingAssembly.GetName();
                _module = name.Name;
                _moduleVersion = String.Format("{0}.{1}.{2}",
                        name.Version.Major, name.Version.Minor, name.Version.Build);
            }
            GetEntryAssemblyInfo();
        }

        internal int EstimateSize()
        {
            return 1024;
        }
    }
}
